from logging import getLogger

from zope.interface import Interface, implements
from zope.component import getSiteManager
from zope.configuration import config, fields
from zope.schema import TextLine, Object

from meercat.mapreduce.interfaces import IReducer 

from meercat.server.rest import interfaces
from meercat.server.rest.mapreduce.reducer import Reducer



log = getLogger('meercat.server.rest.zcml')

class IReducerDirective(Interface):
    name = TextLine(
        title=u'Name',
        description=u'A unique name for the reducer',
        required=True)
    
    formats = fields.Tokens(
        title=u'Formats',
        description=u'Formats reduced by this reducer',
        value_type=TextLine(),
        required=False,
        default=[''])

class IProcessorDirective(Interface):
    """ An extension of the base interface for ZCML usage """
    
    name = TextLine(
        title=u'Name',
        description=u'A name for the processor, unique within a reducer',
        required=True)
    
    factory = fields.GlobalObject(
        title=u'Factory',
        description=u'The factory that creates the processor',
        required=False,
        default=None)
    
    component = fields.GlobalObject(
        title=u'Component',
        description=u'A processor component',
        required=False,
        default=None)
    
    before = TextLine(
        title=u'Before',
        description=u'The name of another processor to insert before',
        required=False,
        default=None)
    
    after = TextLine(
        title=u'After',
        description=u'The name of another processor to insert before',
        required=False,
        default=None)
    
    first = fields.Bool(
        title=u'First',
        description=u'Set to true to insert before all other processors',
        required=False,
        default=None)
    
    last = fields.Bool(
        title=u'Last',
        description=u'Set to true to insert after all other processors',
        required=False,
        default=None)
    
    remove= fields.Bool(
        title=u'Remove',
        description=u'Remove this processor from the list of processors',
        required=False,
        default=False)
    
def processor(context, name, factory=None, component=None, **kwargs):
    if factory and component:
        raise TypeError("Can't specify factory and component.")
    
    processors = context.processors
    
    mapping = {}
    
    for processor in processors:
        mapping[processor.name] = processor
    
    log.debug('ProcessorHandler mapping: %s' % (str(mapping),))
    
    if kwargs.get('remove',False):
        log.debug('Removing processor: %s' % (str(name),))
        if name in mapping:
            processors.remove(mapping[name])
        return
    
    if not (factory or component or name in mapping):
        raise TypeError('Either a component or a factory must be specified or '
                        'the processor must already be registered.')
    
    if factory:
        component = factory()
    
    if not component:
        component = mapping[name]
    
    if not interfaces.IProcessor.providedBy(component):
        raise TypeError('The processor does not provide the IProcessor '
                        'interface')
    
    before = kwargs.get('before',None)
    after = kwargs.get('after',None)
    first = kwargs.get('first',False) or before == '*'
    last = kwargs.get('last',False) or last == '*'
    
    if first or last or before or after:
        log.debug('Removing processor, %s, before re-insertion' % (str(name),))
        if name in mapping and mapping[name] in processors:
            processors.remove(mapping[name])
    elif name in mapping and mapping[name] != component:
        log.debug('Replacing processor, %s, with new one in place' % (str(name),))
        processors[processors.index(mapping[name])] = component
        return
    elif name not in mapping:
        last = True
    
    if first:
        log.debug('Inserting the processor, %s, first.'% (str(name),))
        processors.insert(0,component)
        return
    
    if last:
        log.debug('Inserting the processor, %s, last.' % (str(name),))
        processors.append(component)
        return
    
    if before:
        log.debug('Inserting the processor, %s, before %s' % (str(name),
                                                              str(before),))
        if not before in mapping:
            raise ValueError('%s is not available for inserting before' % (str(before),))
        
        if before == name:
            raise ValueError('A processor cannot be inserted before itself')
        
        processors.insert(processors.index(mapping[before]), component)
        return
    
    if after:
        log.debug('Inserting the processor, %s, after %s' % (str(name),
                                                             str(after),))
        if not after in mapping:
            raise ValueError('%s is not available for inserting after' % (str(after),))
        
        if after == name:
            raise ValueError('A processor cannot be inserted after itself')
        
        processors.insert(processors.index(mapping[after])+1, component)
        return


class ReducerDirective(config.GroupingContextDecorator):
    implements(config.IConfigurationContext, IReducerDirective)
    
    def __init__(self, context, name, formats=None):
        log.debug('%s.__init__(context=%s, name=%s, formats=%s)' % (str(self),
                                                                    str(context),
                                                                    str(name),
                                                                    str(formats),))
        self.context = context
        self.name = name
        self.formats = formats
        self.reducer = None
    
    def before(self):
        log.debug('%s.before()' % (str(self),))
        super(ReducerDirective,self).before()
        
        manager = getSiteManager()
        
        self.reducer = manager.queryUtility(provided=IReducer, name=self.name)
        
        if self.reducer is None or not interfaces.IWebReducer.providedBy(self.reducer):
            self.reducer = Reducer()
            manager.registerUtility(component=self.reducer,
                                    provided=IReducer,
                                    name=self.name)
        
        if self.formats is not None:
            self.reducer.formats = self.formats
    
    def after(self):
        log.debug('%s.after()' % (str(self),))
        super(ReducerDirective,self).after()
